﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using Microsoft.Research.Kinect.Nui;
using System.Text;
using System.Collections;
using Valentia.Kinection.DataTypes;
using Valentia.Kinection.Configuration;

namespace Valentia.Kinection.Manager
{
    /// <summary>
    /// Class used to extract the video, depth and skeleton data from kinect devices.
    /// </summary>
    public class KinectManager
    {
        #region DATA MEMBERS
        /// <summary>
        /// Used for locking purpose to synchronise the starting and stopping the kinect manager.
        /// </summary>
        private readonly Object _StartSync = new Object();

        /// <summary>
        /// Contains the kinect device runtimes,
        /// </summary>
        private Dictionary<String, RuntimeWrapper> kinectRunTimes;
        
        /// <summary>
        /// Contains the locked kinect devices instance keys,
        /// </summary>
        private List<String> lockedKinects;
        
        /// <summary>
        /// Contains the tracked user, tracked ID as key and user index as value
        /// </summary>
        Dictionary<int, int> trackedUserIndexesByID;
        
        /// <summary>
        /// Timout event
        /// </summary>
        private AutoResetEvent stopWait;
        #endregion

        #region EVENTS
        /// <summary>
        /// This event is raised when any notification is generated.
        /// </summary>
        public event EventHandler<NotificationReceivedArgs> NotificationReceived;

        /// <summary>
        /// This event is raised when any kinect device status is changed.
        /// </summary>
        public event EventHandler<KinectDeviceStatusChagnedArgs> KinectDeviceStatusChagned;

        /// <summary>
        /// This event is raised when any kinect device is locked/released for/from data extraction.
        /// </summary>
        public event EventHandler<KinectDeviceLockedArgs> KinectDeviceLocked;

        /// <summary>
        /// This event is raised when video or depth image frame is ready.
        /// </summary>
        public event EventHandler<FrameReceivedArgs> FrameReceived;

        /// <summary>
        /// This event is raised when skeleton image frame is ready.
        /// </summary>
        public event EventHandler<SkeletonFrameReceivedArgs> SkeletonFrameReceived;

        /// <summary>
        /// This event is raised when there is not skeleton data.
        /// </summary>
        public event EventHandler<NoSkeletonFrameArgs> NoSkeletonFrame;
        #endregion

        #region PROEPRTIES
        /// <summary>
        /// Gets true if kinect manager is started to get data from kinect devices, else false.
        /// </summary>
        public bool Started { get; private set; }
        #endregion

        #region CONSTRUCTOR
        /// <summary>
        /// Constructor.
        /// </summary>
        public KinectManager()
        {
            this.kinectRunTimes = new Dictionary<String, RuntimeWrapper>();
            this.lockedKinects = new List<String>();
            this.trackedUserIndexesByID = new Dictionary<int, int>();
            this.stopWait = new AutoResetEvent(false);
            this.Started = false;

            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        }
        #endregion

        #region THREADS
        /// <summary>
        /// Thread to detect any Kinect device attached and initialzes it.
        /// </summary>
        /// <param name="state">Thread state.</param>
        private void DetectKinectThread(Object state)
        {
            try
            {
                lock (this._StartSync)
                {
                    if (!this.Started) return;
                }

                while (true)
                {
                    try
                    {
                        if (!this.Started) return;

                        foreach (RuntimeWrapper runTime in RuntimeWrapper.Kinects)
                        {
                            if (!this.Started) return;

                            KinectDevice kinectDevice = ConfigurationHelper.GetKinectDeviceByName(runTime.Key);
                            if (kinectDevice == null)
                            {
                                kinectDevice =
                                    ConfigurationHelper.SaveKinectDevice(runTime.KinectRuntime.InstanceIndex.ToString(), runTime.KinectRuntime.InstanceName);
                            }

                            if (!this.kinectRunTimes.ContainsKey(runTime.Key)) { this.kinectRunTimes.Add(runTime.Key, runTime); }

                            this.kinectRunTimes[runTime.Key].KinectDeviceEntityObject = kinectDevice;
                            this.InitializeKinectOnStatusChange(runTime.KinectRuntime);
                        }
                    }
                    catch (Exception exp) { this.NotifyException(exp, "Exception while detecting kinect in thread"); }
                    finally
                    {
                        if (this.Started)
                        {
                            this.stopWait.Reset();
                            this.stopWait.WaitOne(5000);
                        }
                    }
                }
            }
            catch (Exception exp)
            {
                if (this.Started)
                {
                    this.NotifyException(exp, "Kinect detect thread terminated unexpectedly");
                }
            }
            finally
            {
                { try { Thread.CurrentThread.Abort(); } catch { } }//Abort current thread and ingore any exception while doing it.
            }
        }
        #endregion

        #region EVENT HANDLER METHODS
        /// <summary>
        ///  Occurs on unhandled exception.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event Args.</param>
        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Exception exp = e.ExceptionObject as Exception;
            if (exp == null) return;

            this.NotifyException(exp, "Unahandlled exception occurred. Terminating: {0}", e.IsTerminating);
        }

        /// <summary>
        /// Called on kinect device status change.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Status changed event args.</param>
        private void Kinects_StatusChanged(object sender, StatusChangedEventArgs e)
        {
            try
            {
                this.InitializeKinectOnStatusChange(e.KinectRuntime);
            }
            catch (Exception exp)
            {
                this.NotifyException(exp, "Exception while detecting kinect device: {0} status changed", e.KinectRuntime.InstanceName);
            }
        }

        /// <summary>
        /// Called when video frame is created.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Image frame ready event args.</param>
        private void KinectRuntime_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            RuntimeWrapper runTimeWrapper = this.GetWrapperFromRuntimeSender(sender);
            if (runTimeWrapper == null) return;

            this.OnVideoFrameReady(runTimeWrapper, e);
        }
        
        /// <summary>
        /// Raises the events related to video frame received.
        /// </summary>
        /// <param name="runTime">Runtime.</param>
        /// <param name="e">Image frame ready event args.</param>
        private void OnVideoFrameReady(RuntimeWrapper runTime, ImageFrameReadyEventArgs e)
        {
            try
            {
                if (!this.Started || !this.kinectRunTimes.ContainsKey(runTime.Key) || !runTime.AllowedVideoStream) return;

                Bitmap bitmap = runTime.GetBitmap(e.ImageFrame, false);
                if (this.FrameReceived != null)
                {
                    this.FrameReceived(
                        this,
                        new FrameReceivedArgs(FrameReceivedArgs.FrameReceivedType.Video, (KinectImageResolution)runTime.VideoImageResoloution, runTime.Key, bitmap));
                }
            }
            catch (Exception exp)
            {
                this.NotifyException(exp, "Exception while video frame received from kinect device: {0}", runTime.Key);
            }
        }

        /// <summary>
        /// Called when depth frame is created.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Image frame ready event args.</param>
        private void KinectRuntime_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
        {
            RuntimeWrapper runTimeWrapper = this.GetWrapperFromRuntimeSender(sender);
            if (runTimeWrapper == null) return;

            this.OnDepthFrameReady(runTimeWrapper, e);
        }

        /// <summary>
        /// Raises the events related to depth frame received.
        /// </summary>
        /// <param name="runTime">Runtime.</param>
        /// <param name="e">Image frame ready event args.</param>
        private void OnDepthFrameReady(RuntimeWrapper runTime, ImageFrameReadyEventArgs e)
        {
            try
            {
                if (!this.Started || !this.kinectRunTimes.ContainsKey(runTime.Key) || !runTime.AllowedDepthStream) return;

                Bitmap bitmap = runTime.GetBitmap(e.ImageFrame, true);
                if (this.FrameReceived != null)
                {
                    this.FrameReceived(
                        this,
                        new FrameReceivedArgs(
                            FrameReceivedArgs.FrameReceivedType.Depth, (KinectImageResolution)runTime.DepthImageResoloution, runTime.Key, bitmap,
                            e.ImageFrame.Image.BytesPerPixel, e.ImageFrame.Image.Width, e.ImageFrame.Image.Height, e.ImageFrame.Image.Bits));
                }
            }
            catch (Exception exp)
            {
                this.NotifyException(exp, "Exception while depth frame received from kinect device: {0}", runTime.Key);
            }
        }

        /// <summary>
        /// Called when skeleton frame is created.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Image frame ready event args.</param>
        private void KinectRuntime_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
        {
            RuntimeWrapper runTimeWrapper = this.GetWrapperFromRuntimeSender(sender);
            if (runTimeWrapper == null) return;

            this.OnSkeletonFrameReady(runTimeWrapper, e);
        }

        /// <summary>
        /// Raises the events related to skeleton frame received.
        /// </summary>
        /// <param name="runTime">Runtime.</param>
        /// <param name="e">Image frame ready event args.</param>
        private void OnSkeletonFrameReady(RuntimeWrapper runTime, SkeletonFrameReadyEventArgs e)
        {
            try
            {
                if (!this.Started || !this.kinectRunTimes.ContainsKey(runTime.Key) || !runTime.AllowedSkeletonStream) return;

                SkeletonFrame skeletonFrame = e.SkeletonFrame;
                //if power is removed from the Kinect, you may still get an event here, but skeletonFrame will be null.
                if (skeletonFrame == null) return;

                Dictionary<int, int> nowtrackedUserIndexesByID = new Dictionary<int, int>();
                StringBuilder stringBuilder = new StringBuilder(String.Empty);

                List<SkeletonJointCollection> skeletons = new List<SkeletonJointCollection>();
                foreach (SkeletonData data in skeletonFrame.Skeletons)
                {
                    if (data.TrackingState != SkeletonTrackingState.Tracked) continue;

                    SkeletonJointCollection jointCollection = runTime.GetSkeletonPoints(data);

                    nowtrackedUserIndexesByID.Add(jointCollection.TrackingID, jointCollection.UserIndex);
                    if (!this.trackedUserIndexesByID.ContainsKey(jointCollection.TrackingID))
                    {
                        this.trackedUserIndexesByID.Add(jointCollection.TrackingID, jointCollection.UserIndex);
                        stringBuilder.Append(
                            String.Format(
                            "Kinect device: {0} trakced skeleton with tracking ID: {1}, and user index: {2}{3}", runTime.Key, jointCollection.TrackingID,
                            jointCollection.UserIndex, Environment.NewLine));
                    }
                    skeletons.Add(jointCollection);
                }

                if (skeletons.Count > 0)
                {
                    runTime.NoSkeletonReported = false;
                    if (this.SkeletonFrameReceived != null)
                    {
                        this.SkeletonFrameReceived(
                            this, new SkeletonFrameReceivedArgs(runTime.Key, ((KinectImageResolution)(runTime.VideoImageResoloution)), skeletons.ToArray()));
                    }
                }
                else if (!runTime.NoSkeletonReported)
                {
                    if (this.NoSkeletonFrame != null) { this.NoSkeletonFrame(this, new NoSkeletonFrameArgs(runTime.Key)); }
                    runTime.NoSkeletonReported = true;
                }

                if (stringBuilder.Length > 0)
                {
                    this.NotifyAndLog(NotificationType.SkeletonTracked, stringBuilder.ToString().Trim());
                    stringBuilder.Clear();
                }

                int userIndex = 0;
                foreach (int trackingID in new Hashtable(this.trackedUserIndexesByID).Keys)
                {
                    if (nowtrackedUserIndexesByID.ContainsKey(trackingID)) continue;

                    userIndex = this.trackedUserIndexesByID[trackingID];
                    this.trackedUserIndexesByID.Remove(trackingID);
                    stringBuilder.Append(
                        String.Format(
                        "Kinect device: {0} released skeleton with tracking ID: {1}, and user index: {2}{3}", runTime.Key, trackingID, userIndex,
                        Environment.NewLine));
                }

                if (stringBuilder.Length > 0)
                {
                    this.NotifyAndLog(NotificationType.SkeletonReleased, stringBuilder.ToString().Trim());
                    stringBuilder.Clear();
                }
            }
            catch (Exception exp)
            {
                this.NotifyException(exp, "Exception while skeleton frame received from kinect device: {0}", runTime.Key);
            }
        }
        #endregion

        #region PRIVATE METHODS
        /// <summary>
        /// Gets the run time wrapper instance from the sender of run time event handler methods.
        /// </summary>
        /// <param name="sender">Runtime sender in passed in event handler method.</param>
        /// <returns>Run time wrapper instance.</returns>
        private RuntimeWrapper GetWrapperFromRuntimeSender(object sender)
        {
            Runtime runTime = sender as Runtime;
            if (runTime == null) return null;

            RuntimeWrapper runTimeWrapper = null;
            if (!this.kinectRunTimes.TryGetValue(RuntimeWrapper.GetKey(runTime), out runTimeWrapper) || runTimeWrapper == null) return null;

            return runTimeWrapper;
        }

        /// <summary>
        /// Initilizes the kinect run time if status is valid.
        /// </summary>
        /// <param name="runTime">Kinect run time object to initialize</param>
        private void InitializeKinectOnStatusChange(Runtime runTime)
        {
            if (!this.Started) return;

            String key = String.Empty;
            RuntimeWrapper runTimeWrapper = null;

            try
            {
                key = RuntimeWrapper.GetKey(runTime);
                if (!this.kinectRunTimes.ContainsKey(key)) return;

                runTimeWrapper = this.kinectRunTimes[key];
            }
            catch (Exception exp)
            {
                this.NotifyException(exp, "Exception occurred for Kinect device: {0}", key);
                return;
            }

            try
            {
                Monitor.Enter(runTimeWrapper);
                runTimeWrapper.KinectRuntime = runTime;

                if (!this.CheckKinectStatus(runTimeWrapper)) return;
                if (this.lockedKinects.Contains(runTimeWrapper.Key)) return; // Already initialized so return

                runTimeWrapper.Initialize();
                runTimeWrapper.KinectRuntime.VideoFrameReady += new EventHandler<ImageFrameReadyEventArgs>(KinectRuntime_VideoFrameReady);
                runTimeWrapper.KinectRuntime.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(KinectRuntime_DepthFrameReady);
                runTimeWrapper.KinectRuntime.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(KinectRuntime_SkeletonFrameReady);

                if (!this.lockedKinects.Contains(runTimeWrapper.Key)) { this.lockedKinects.Add(runTimeWrapper.Key); }

                this.OnKinectLockedOrReleased(runTimeWrapper.Key, true);
                this.NotifyAndLog(NotificationType.KinectLocked, "Data extraction from kinect: {0} is started", runTimeWrapper.Key);
            }
            catch (Exception exp)
            {
                this.StopKinect(runTimeWrapper, false, false);

                if (exp is COMException)
                {
                    COMException comException = exp as COMException;
                    if (comException.ErrorCode == -2147220947)  //Runtime is being used by another app.
                    {
                        this.NotifyAndLog(
                            NotificationType.Kinect_Already_In_Use, "Kinect device: {0} is being already used by other application",
                            runTimeWrapper.Key);
                        return;
                    }
                }

                this.NotifyException(exp, "Exception while initilizing the kinect device: {0}", runTime.InstanceName);
            }
            finally { Monitor.Exit(runTimeWrapper); }
        }

        /// <summary>
        /// Checks the kinet status whether the kinect can be used to extract data or not.
        /// <para>Only True will be return for kinect device with status 'Connected'</para>
        /// </summary>
        /// <param name="runTime">Kinect run time object.</param>
        /// <returns>Returns true if kinect can be used to extract data else flase.</returns>
        private bool CheckKinectStatus(RuntimeWrapper runTime)
        {
            KinectDeviceStatus status = (KinectDeviceStatus)(runTime.KinectRuntime.Status);
            if (!runTime.Active) { status = KinectDeviceStatus.NotActive; }

            if (!runTime.KinectStatus.HasValue || runTime.KinectStatus.Value != status)
            {
                runTime.KinectStatus = status;
                try
                {
                    if (this.KinectDeviceStatusChagned != null)
                    {
                        this.KinectDeviceStatusChagned(this, new KinectDeviceStatusChagnedArgs(runTime.Key, (KinectDeviceStatus)(status)));
                    }
                }
                catch (Exception exp)
                {
                    this.NotifyException(exp, "Exception while raising event for KinectDeviceStatusChagned");
                }

                this.NotifyAndLog(NotificationType.KinectStatusChanged, "Kinect deivce: {0} status changed to: {1}", runTime.Key, status.ToString());
            }

            bool canBeUsed = (status == KinectDeviceStatus.Connected);

            if (!canBeUsed && this.lockedKinects.Contains(runTime.Key)) { this.StopKinect(runTime, false, false); }
            return canBeUsed;
        }

        /// <summary>
        /// Unintialize the kinect device and remove from the collection if exists.
        /// </summary>
        /// <param name="runTime">Kinet run time to stop.</param>
        /// <param name="removeFromCollection">True to remove from collection if exists.</param>
        /// <param name="notify">True to notify the stop event, else false.</param>
        private void StopKinect(RuntimeWrapper runTime, bool removeFromCollection, bool notify)
        {
            if (removeFromCollection && this.kinectRunTimes.ContainsKey(runTime.Key)) { this.kinectRunTimes.Remove(runTime.Key); }

            try
            {
                runTime.Uninitialize();
                { try { runTime.KinectRuntime.VideoFrameReady -= new EventHandler<ImageFrameReadyEventArgs>(KinectRuntime_VideoFrameReady); } catch { } }
                { try { runTime.KinectRuntime.DepthFrameReady -= new EventHandler<ImageFrameReadyEventArgs>(KinectRuntime_DepthFrameReady); } catch { } }
                { try { runTime.KinectRuntime.SkeletonFrameReady -= new EventHandler<SkeletonFrameReadyEventArgs>(KinectRuntime_SkeletonFrameReady); } catch { } }
            }
            catch { } //ignore any exception while uninitializing

            if (this.lockedKinects.Contains(runTime.Key))
            {
                this.lockedKinects.Remove(runTime.Key);
                this.OnKinectLockedOrReleased(runTime.Key, false);
            }

            if (notify) { this.NotifyAndLog(NotificationType.KinectReleased, "Data extraction from kinect: {0} is stopped", runTime.Key); }
        }

        /// <summary>
        /// This method is called when kinect device is locked/released for/from data extracting.
        /// </summary>
        /// <param name="kinectDevice">Kinect device name.</param>
        /// <param name="locked">True if locked else false if released.</param>
        private void OnKinectLockedOrReleased(String kinectDevice, bool locked)
        {
            try
            {
                if (this.KinectDeviceLocked != null) { this.KinectDeviceLocked(this, new KinectDeviceLockedArgs(kinectDevice, locked)); }
            }
            catch (Exception exp)
            {
                this.NotifyException(exp, "Exception while raising event for KinectDeviceLocked");
            }
        }

        /// <summary>
        /// Notifies the user of this class by message. Also logs the notification event.
        /// </summary>
        /// <param name="notificationType">Notification type.</param>
        /// <param name="notificationStringFormat">Notification string format.</param>
        /// <param name="args">Arguments.</param>
        private void NotifyAndLog(NotificationType notificationType, String notificationStringFormat, params Object[] args)
        {
            try
            {
                String message = String.Format(notificationStringFormat, args);
                if (this.NotificationReceived != null) { this.NotificationReceived(this, new NotificationReceivedArgs(notificationType, message, null)); }
            }
            catch { }
        }

        /// <summary>
        ///  Generates notification for exception
        /// </summary>
        /// <param name="exp">Exception</param>
        /// <param name="notificationStringFormat">Notification string format</param>
        /// <param name="args">Arguments</param>
        private void NotifyException(Exception exp, String notificationStringFormat, params Object[] args)
        {
            try
            {
                String message = String.Format(notificationStringFormat, args);
                if (this.NotificationReceived != null) { this.NotificationReceived(this, new NotificationReceivedArgs(NotificationType.Exception, message, exp)); }
            }
            catch { }
        }
        #endregion

        #region PUBLIC METHODS
        /// <summary>
        /// Starts the ongoing process of detecting Kinect devices and extracting data.
        /// </summary>
        public void Start()
        {
            lock (this._StartSync)
            {
                if (this.Started) return;

                this.stopWait.Reset();

                // Launches thread to detect kinect device connected
                ThreadPool.QueueUserWorkItem(this.DetectKinectThread);
                this.Started = true;

                Runtime.Kinects.StatusChanged += new EventHandler<StatusChangedEventArgs>(Kinects_StatusChanged);
                this.NotifyAndLog(NotificationType.DataExtractorStarted, "Data extractor for kinect is started");
            }
        }

        /// <summary>
        /// Stops the ongoing process of detecting Kinect devices and extracting data.
        /// </summary>
        public void Stop()
        {
            lock (this._StartSync)
            {
                this.Started = false;
                this.stopWait.Set();

                RuntimeWrapper[] allRunTimes = new RuntimeWrapper[this.kinectRunTimes.Count];
                this.kinectRunTimes.Values.CopyTo(allRunTimes, 0);
                this.kinectRunTimes.Clear();

                foreach (RuntimeWrapper runTime in allRunTimes) { this.StopKinect(runTime, false, false); }

                this.lockedKinects.Clear();
                this.trackedUserIndexesByID.Clear();

                Runtime.Kinects.StatusChanged -= new EventHandler<StatusChangedEventArgs>(Kinects_StatusChanged);
                this.NotifyAndLog(NotificationType.DataExtractorStopped, "Data extractor for kinect is stopped");
            }
        }

        /// <summary>
        /// Moves the first kinect device up by increasing its ElevationAngle.
        /// </summary>
        public void MoveKinectUp()
        {
            if (!this.Started || this.kinectRunTimes.Count <= 0) return;
            foreach (RuntimeWrapper runTime in new Hashtable(this.kinectRunTimes).Values) { this.MoveKinectUpOrDown(runTime.Key, true, 2); }
        }

        /// <summary>
        /// Moves the specified kinect device up by increasing its ElevationAngle.
        /// </summary>
        /// <param name="kinectDeviceName">Kinect device name to move up.</param>
        public void MoveKinectUp(String kinectDeviceName)
        {
            this.MoveKinectUpOrDown(kinectDeviceName, true, 2);
        }

        /// <summary>
        /// Moves the first kinect device up by decreasing its ElevationAngle.
        /// </summary>
        public void MoveKinectDown()
        {
            if (!this.Started || this.kinectRunTimes.Count <= 0) return;
            foreach (RuntimeWrapper runTime in new Hashtable(this.kinectRunTimes).Values) { this.MoveKinectUpOrDown(runTime.Key, false, 2); }
        }

        /// <summary>
        /// Moves the specified kinect device up by decreasing its ElevationAngle.
        /// </summary>
        /// <param name="kinectDeviceName">Kinect device name to move down.</param>
        public void MoveKinectDown(String kinectDeviceName)
        {
            this.MoveKinectUpOrDown(kinectDeviceName, false, 2);
        }

        /// <summary>
        /// Moves the specified kinect device up or down by increasing or decreasing its ElevationAngle.
        /// </summary>
        /// <param name="kinectDeviceName">Kinect device name to move up or down.</param>
        /// <param name="moveUp">True to move up and false to move down.</param>
        /// <param name="value">Value to increment or decrement. This value must be positive.</param>
        public void MoveKinectUpOrDown(String kinectDeviceName, bool moveUp, int value)
        {
            try
            {
                if (!this.Started || this.kinectRunTimes.Count <= 0) return;

                RuntimeWrapper runTime = null;
                if (!this.kinectRunTimes.TryGetValue(kinectDeviceName, out runTime)) return;

                if (value < 0) { value *= -1; }//if value is negative then making it positive.
                value *= moveUp ? 1 : -1;

                runTime.KinectRuntime.NuiCamera.ElevationAngle += value;
            }
            catch { }//ignore exception here.
            //catch (Exception exp)
            //{
            //    this.NotifyException(
            //        exp, "Exception while changing elevation for kinect device: {0}. Direction was: {1}. Value: {2}", kinectDeviceName, moveUp ? "Up" : "Down", value);
            //}
        }

        /// <summary>
        /// Returns connected kinect devices list.
        /// </summary>
        /// <returns>List of kinect devices.</returns>
        public List<KinectDeviceInfo> GetKinectDevices()
        {
            List<KinectDeviceInfo> kinectDevicesInfo = new List<KinectDeviceInfo>();
            try
            {
                foreach (RuntimeWrapper kinectDevice in new Hashtable(kinectRunTimes).Values)
                {
                    if (!kinectDevice.KinectStatus.HasValue) continue;

                    kinectDevicesInfo.Add(
                        new KinectDeviceInfo(kinectDevice.Key, kinectDevice.KinectRuntime.InstanceName, kinectDevice.KinectStatus.Value, kinectDevice.Initilized));
                }
            }
            catch (Exception exp) { this.NotifyException(exp, "Exception while loading kinect devices info"); }

            return kinectDevicesInfo;
        }

        /// <summary>
        /// Used to identify connected kinect devices count.
        /// </summary>
        /// <param name="connectedDevicesCount">Connected devices count.</param>
        /// <returns>True if atleast one kinect device is connected else false.</returns>
        public bool IsAnyKinectDeviceConnected(out int connectedDevicesCount)
        {
            connectedDevicesCount = 0;
            try
            {
                foreach (RuntimeWrapper kinectDevice in new Hashtable(kinectRunTimes).Values)
                {
                    if (!kinectDevice.KinectStatus.HasValue || kinectDevice.KinectStatus.Value != KinectDeviceStatus.Connected) continue;

                    connectedDevicesCount++;
                }
            }
            catch (Exception exp) { this.NotifyException(exp, "Exception while checking kinect device is online"); }

            return (connectedDevicesCount > 0);
        }
        #endregion
    }
}